// A simple car motion in 3D with 4 degree of freedom
// Dependency: GLM, porsche.obj. porsche.mtl, TGA

#include <math.h>
#include <stdlib.h>

#include <string>
using std::string;

#include <GL/freeglut.h> 
#include <GL/glut.h>
#include <stdio.h> 
#include "lib/glm.c"
#include "lib/tga.h"
#include "lib/tga.c"

#define UPDATE_RATE 20 
#define PI_180  0.017453

// 3D model

GLMmodel* pmodel = NULL;

//Fungsi membaca model tiga dimensi & tekstur
	void loadEverything(void)
{
    glEnable (GL_DEPTH_TEST);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);	
	
    if(!pmodel)
    {
        pmodel = glmReadOBJ("data/porsche.obj");
        if(!pmodel) exit(0);
        glmUnitize(pmodel);
        glmFacetNormals(pmodel);
        glmVertexNormals(pmodel, 90.0);
    }
    
    loadTGA("data/floor.tga", 1);

}


// Camera

GLfloat camera_distance = 6, camera_angle = 0;; 
static GLfloat px=0.0f,py=2.5,pz = -camera_distance;   // koordinat mata
static GLfloat lx=0.0f,ly=0.0f,lz=0.0f;
static GLfloat nv_x=0.0, nv_y=1, nv_z=0.0;  // normal vectors for LookAt()
  
GLfloat altitude = 0.5; 
 
GLfloat x_speed = 0, y_speed = 0, z_speed = 0.0; 
GLfloat x_accel = 0, y_accel = 0, z_accel = 0.0;
GLfloat direction = 0;

GLfloat constant_speed = 0.0;
GLfloat max_speed = 0.2;
GLfloat min_speed = 0.0;
GLfloat max_backward_speed = -0.08;

GLfloat x_pos = 0, y_pos = 0, z_pos = 0;
GLfloat steering = 1;
GLfloat max_steering = 2;

int UP_KEYDOWN = 0, UP_KEYUP = 0, DN_KEYDOWN = 0, DN_KEYUP = 0;
int LEFT_KEYDOWN = 0, LEFT_KEYUP = 0, RIGHT_KEYDOWN = 0, RIGHT_KEYUP = 0;
int in_boundary = 0;
 
char debugString[3][255]; 

const float Z_MIN = -100, Z_MAX = 100;
const float X_MIN = -100, X_MAX = 100;
 
// Lighting

const GLfloat light_ambient[]  = { 0.0f, 0.0f, 0.0f, 1.0f };
const GLfloat light_diffuse[]  = { 1.0f, 1.0f, 1.0f, 1.0f };
const GLfloat light_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
const GLfloat light_position[] = { 0.0f, 25.0f, 15.0f, 0.0f };

const GLfloat mat_ambient[]    = { 0.7f, 0.7f, 0.7f, 1.0f };
const GLfloat mat_diffuse[]    = { 0.8f, 0.8f, 0.8f, 1.0f };
const GLfloat mat_specular[]   = { 1.0f, 1.0f, 1.0f, 1.0f };
const GLfloat high_shininess[] = { 100.0f }; 
 
//Mempertahankan proporsi objek ketika dimensi layar dirubah
 
void changeSize(int w, int h) {
 
        // Prevent a divide by zero, when window is too short
        // (you cant make a window of zero width).
        if(h == 0)
                h = 1;
 
        float ratio = 1.0* w / h;
 
        // Reset the coordinate system before modifying
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        
        // Set the viewport to be the entire window
        glViewport(0, 0, w, h);
 
        // Set the correct perspective.
        gluPerspective(45,ratio,1,1000);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        gluLookAt(px,py,pz, 
                      px+lx,py+ly,pz+lz,
                          nv_x,nv_y,nv_z);
 
}

void printBitmap(const string msg, double x, double y)
{
    glRasterPos2d(x, y);
    for (string::const_iterator ii = msg.begin();
         ii != msg.end();
         ++ii)
    {
        glutBitmapCharacter(GLUT_BITMAP_8_BY_13, *ii);
    }
}

void world() {

		int tilesize = 50;
		int x, z;

        glEnable(GL_TEXTURE_2D);                                // Enable texture mapping
        glBindTexture(GL_TEXTURE_2D,1);                 // Bind ke texture n
        
        for(x = X_MIN; x < X_MAX; x += tilesize)
        {
			for(z = Z_MIN; z < Z_MAX; z += tilesize)
			{
				glBegin(GL_QUADS);
					glTexCoord2f(0.0f,0.0f); glVertex3f(x, 0, z);
					glTexCoord2f(1.0f,0.0f); glVertex3f(x+tilesize, 0, z);
					glTexCoord2f(1.0f,1.0f); glVertex3f(x+tilesize, 0, z+tilesize);
					glTexCoord2f(0.0f,1.0f); glVertex3f(x, 0, z+tilesize);
				glEnd();
			}
		}
		
        glDisable(GL_TEXTURE_2D);
    
}

void car() {
   
    glmDraw(pmodel, GLM_SMOOTH | GLM_MATERIAL);
    glEnable(GL_COLOR_MATERIAL);
    
}

void carShadow() {
	
	glColor3f(0.2, 0.2, 0.2);
	glPushMatrix();
	glScalef(0.9,1,0.9);
	glBegin(GL_POLYGON);
		glVertex3f(-0.8, -0.45, 1.289);
		glVertex3f(-0.588, -0.45, 1.5);
		glVertex3f(0.588, -0.45, 1.5);
		glVertex3f(0.8, -0.45, 1.289);
		glVertex3f(0.8, -0.45, -1.289);
		glVertex3f(0.588, -0.45, -1.5);
		glVertex3f(-0.588, -0.45, -1.5);
		glVertex3f(-0.8, -0.45, -1.289);
	glEnd();
	glPopMatrix();
	
}
 
void renderScene(void) {
 
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    world();
 
    glPushMatrix();
        glTranslatef(x_pos, altitude, z_pos);
        glRotatef(direction, 0, 1, 0);
        carShadow();
        glScalef(1.7, 1.7, 1.7);
        car();
    glPopMatrix();
 
    // Draw OSD
    glDisable(GL_DEPTH_TEST);
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0., 1., 0., 1.);
    glColor3d(1, 1, 1); 

    printBitmap(debugString[0], 0.02, 0.94);
    printBitmap(debugString[1], 0.02, 0.91);
//    printBitmap("Move with arrow keys. Q, W for zooming.", 0.02, 0.03);

    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glEnable(GL_DEPTH_TEST);
    
    glutSwapBuffers();
 
}
 
void turnLeft() {
    
    if (steering <= max_steering)
        steering += 0.05;
    
    if (camera_angle >= -0.2)    
		camera_angle -= 0.003;
        
    direction += 3.5 * constant_speed * steering;
}

void turnLeftRelease() {
    
    if (steering >= 1 && !RIGHT_KEYDOWN)
        steering -= 0.1; 
                 
}

void turnRight() {

    if (camera_angle <= 0.2)    
		camera_angle += 0.003;

    if (steering <= max_steering)
        steering += 0.05;   
    
    direction -= 3.5 * constant_speed * steering;
}

void turnRightRelease() {

    if (steering >= 1 && !LEFT_KEYDOWN)
        steering -= 0.1; 

}

void throttle() {
    
    if (constant_speed <= max_speed)
        constant_speed += 0.001;
        
}

void throttleRelease() {
    if (constant_speed >= min_speed)
        constant_speed -= 0.001;    
}

void backward() {
    if (constant_speed >= max_backward_speed)
        constant_speed -= 0.001;
    
}

void backwardRelease() {
    if (constant_speed <= min_speed)
        constant_speed += 0.001;
    else {
        constant_speed = min_speed;
        DN_KEYUP = 0;   // workaround
    };
    
}

 
void inputKey(int key, int x, int y) {
 
        switch (key) {

                case GLUT_KEY_UP : 
                        UP_KEYDOWN = 1;
                        UP_KEYUP = 0;
                        break;

                case GLUT_KEY_DOWN : 
                        DN_KEYDOWN = 1;
                        DN_KEYUP = 0;
                        break;

                case GLUT_KEY_LEFT : 
                        LEFT_KEYDOWN = 1;
                        LEFT_KEYUP = 0;
                        break;

                case GLUT_KEY_RIGHT : 
                        RIGHT_KEYDOWN = 1;
                        RIGHT_KEYUP = 0;
                        break;
 
        }
 
}

void inputKeyUp(int key, int x, int y) {
 
        switch (key) {

                case GLUT_KEY_UP : 
                        UP_KEYDOWN = 0;
                        UP_KEYUP = 1;
                        break;

                case GLUT_KEY_DOWN : 
                        DN_KEYDOWN = 0;
                        DN_KEYUP = 1;
                        break;

                case GLUT_KEY_LEFT : 
                        LEFT_KEYDOWN = 0;
                        LEFT_KEYUP = 1;
                        break;

                case GLUT_KEY_RIGHT : 
                        RIGHT_KEYDOWN = 0;
                        RIGHT_KEYUP = 1;
                        break;

 
        }
 
}

// for camera
void keyFunc(unsigned char key, int x, int y) {
 
        switch (key) {

                case 'q' : 
                        camera_distance += 0.5;
                        break;

                case 'w' : 
                        camera_distance -= 0.5;
                        break;
  
        }
 
}

void timer(int extra)
{

    // magics...

    if (UP_KEYDOWN) throttle();
    if (UP_KEYUP) throttleRelease();
    
    if (DN_KEYDOWN) backward();
    if (DN_KEYUP) backwardRelease();
    
    if (LEFT_KEYDOWN) turnLeft();
    if (LEFT_KEYUP) turnLeftRelease();
        
    if (RIGHT_KEYDOWN) turnRight();
    if (RIGHT_KEYUP) turnRightRelease();

    z_speed = cos(direction * PI_180) * constant_speed;
    x_speed = sin(direction * PI_180) * constant_speed;

    z_pos += z_speed;
    x_pos += x_speed;

    // edge detection (not realistic yet)
    
    if (x_pos >= X_MAX - 2.5) {x_pos = X_MAX - 2.5; x_speed = 0;}
    if (x_pos <= X_MIN + 2.5) {x_pos = X_MIN + 2.5; x_speed = 0;}
    if (z_pos >= Z_MAX - 2.5) {z_pos = Z_MAX - 2.5; z_speed = 0;}
    if (z_pos <= Z_MIN + 2.5) {z_pos = Z_MIN + 2.5; z_speed = 0;}
    
    glLoadIdentity();
    gluLookAt(
        x_pos - (camera_distance + constant_speed/max_speed) * sin(direction*PI_180 + camera_angle), 	// panjang amat sih
        py, 
        z_pos - (camera_distance + constant_speed/max_speed) * cos(direction*PI_180 + camera_angle),
        lx + x_pos, ly, lz + z_pos, 
        nv_x,nv_y,nv_z
    );                       
    sprintf(debugString[0], "ZPOS = %.2f, ZSPEED = %.2f, XPOS = %.2f, XSPEED = %.2f", z_pos, z_speed, x_pos, x_speed);
    sprintf(debugString[1], "DIRECTION = %.2f, SPEED = %.2f, STEERING = %.2f", direction, constant_speed, steering);
   
    glutPostRedisplay();
    glutTimerFunc(UPDATE_RATE,timer,0);
} 

 
int main(int argc, char **argv) {
 
        glutInit(&argc, argv);
 
        // This is where we say that we want a double buffer
        glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);
 
        glutInitWindowPosition(100,100);
        glutInitWindowSize(600,600);
        glutCreateWindow("A porsche");
        
        loadEverything();
 
        glutDisplayFunc(renderScene);
 
        // anti-alias
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable(GL_LINE_SMOOTH);
    
        glutTimerFunc(0,timer,0);

        glutReshapeFunc(changeSize);
        glutSpecialFunc(inputKey);
        glutSpecialUpFunc(inputKeyUp);
        glutKeyboardFunc(keyFunc);
        glutIgnoreKeyRepeat(1);
 
        // enable depth testing
 
        glEnable(GL_DEPTH_TEST);
 
        glClearColor(0,0,0,0);
     
        //Pencahayaan, dari proyek default GLUT  
         glEnable(GL_DEPTH_TEST);
         glDepthFunc(GL_LESS);
         glEnable(GL_LIGHT0);
         glEnable(GL_NORMALIZE);
         glEnable(GL_COLOR_MATERIAL);
         glEnable(GL_LIGHTING);

         glLightfv(GL_LIGHT0, GL_AMBIENT,  light_ambient);
         glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_diffuse);
         glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
         glLightfv(GL_LIGHT0, GL_POSITION, light_position);

         glMaterialfv(GL_FRONT, GL_AMBIENT,   mat_ambient);
         glMaterialfv(GL_FRONT, GL_DIFFUSE,   mat_diffuse);
         glMaterialfv(GL_FRONT, GL_SPECULAR,  mat_specular);
         glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);
     
        glutMainLoop();
     
        return 0;
 
}
